﻿/////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009 Eric Bowen
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, including 
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 
// following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Author's blog: http://blog.scrappydog.com
// Updated source code available:  http://scrappydb.codeplex.com
/////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Amazon.SimpleDB.Model;
using Amazon.SimpleDB.Util;
using NLog;

namespace ScrappyDB.Utilities
{
    public class Utility
    {
        private static readonly Logger Log = LogManager.GetCurrentClassLogger();

        private const int MaxDigitsLeft = 10;
        private const int MaxDigitsRight = 5;
        private const int OffsetValue = 1000000000;

        private static readonly Regex IsGuidRegex =
            new Regex(
                @"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$",
                RegexOptions.Compiled);

        internal static bool IsGuid(string candidate, out Guid output)
        {
            bool isValid = false;

            output = Guid.Empty;

            if (candidate != null)
            {
                if (IsGuidRegex.IsMatch(candidate))
                {
                    output = new Guid(candidate);

                    isValid = true;
                }
            }

            return isValid;
        }

        public static void TraceResponse(ResponseMetadata responseMetadata, string xml)
        {
            {
                if (Utilities.Configuration.DefaultTraceSwitch.TraceVerbose)
                {

                    Log.Debug("ResponseMetadata");
                    if (responseMetadata.IsSetRequestId())
                    {
                        Log.Debug("RequestId: {0}", responseMetadata.RequestId);
                    }
                    if (responseMetadata.IsSetBoxUsage())
                    {
                        Log.Debug("BoxUsage:  {0}", responseMetadata.BoxUsage);
                    }
                    try
                    {
                        Log.Debug(xml);
                    }
                    catch (Exception ex)
                    {
                        Log.Warn("TraceResponse Error: {0}", ex.Message);
                    }
                }
            }
        }

        public static string EncodeNumeric(ushort value)
        {
            var maxLength = ushort.MaxValue.ToString().Length;
            var result = value.ToString().PadLeft(maxLength, '0');
            return result;
        }

        public static string EncodeNumeric(ulong value)
        {
            var maxLength = ulong.MaxValue.ToString().Length;
            var result = value.ToString().PadLeft(maxLength, '0');
            return result;
        }

        public static string EncodeNumeric(Int16 value)
        {
            return EncodeNumericInt16(value);
        }
        public static string EncodeNumericInt16(Int16 value)
        {
            Int32 minLength = Math.Abs((Int32)Int16.MinValue).ToString().Length;
            Int32 offset = (Int32)Math.Pow(10, minLength);
            Int32 maxLength = (Int16.MaxValue + offset).ToString().Length;
            string result = (value + offset).ToString().PadLeft(maxLength, '0');
            return result;
        }

        public static string EncodeNumeric(Int32 value)
        {
            return EncodeNumericInt32(value);
        }

        public static string EncodeNumericInt32(Int32 value)
        {
            Int32 minLength = Math.Abs((Int64)Int32.MinValue).ToString().Length;
            Int64 offset = (Int64)Math.Pow(10, minLength);
            Int32 maxLength = (Int32.MaxValue + offset).ToString().Length;
            string result = (value + offset).ToString().PadLeft(maxLength, '0');
            return result;
        }

        public static Int16 DecodeNumericInt16(string encoded)
        {
            Int32 minLength = Math.Abs((Int32)Int16.MinValue).ToString().Length;
            Int32 offset = (Int32)Math.Pow(10, minLength);

            Int32 rawValue = Int32.Parse(encoded);
            Int16 result = (Int16)(rawValue - offset);
            return result;
        }

        public static Int32 DecodeNumericInt32(string encoded)
        {
            Int32 minLength = Math.Abs((Int64)Int32.MinValue).ToString().Length;
            Int64 offset = (Int64)Math.Pow(10, minLength);

            Int64 rawValue = Int64.Parse(encoded);
            Int32 result = (Int32)(rawValue - offset);
            return result;
        }

        public static void DecodeNumeric(string value, out ulong result)
        {
            throw new NotImplementedException();
            //result = AlternateSimpleDBCode.DecodeRealNumberRangeDouble(value, MaxDigitsRight, OffsetValue);
        }

        public static string EncodeNumeric(double value)
        {
            string result = EncodeRealNumberRange(value, MaxDigitsLeft, MaxDigitsRight, OffsetValue);
            return result;
        }

        public static double DecodeNumeric(string value)
        {
            double result = AlternateSimpleDBCode.DecodeRealNumberRangeDouble(value, MaxDigitsRight, OffsetValue);
            return result;
        }

        public static String EncodeRealNumberRange(double number, int maxDigitsLeft, int maxDigitsRight, int offsetValue)
        {
            var shiftMultiplier = (long)Math.Pow(10, maxDigitsRight);

            var minValue = (offsetValue * -1);
            if (number < minValue)
                throw new ArgumentException("EncodeRealNumberRange can not encode numbers less than " + minValue.ToString());

            var shiftedNumber = (long)Math.Round((number + offsetValue) * shiftMultiplier);

            //result should always be positive
            if (shiftedNumber < 0)
                throw new ArgumentException();

            if (shiftedNumber >= (long)Math.Pow(10, maxDigitsLeft + maxDigitsRight + 1))
                throw new ArgumentException();

            return shiftedNumber.ToString().PadLeft(maxDigitsLeft + maxDigitsRight, '0');
        }

        // Create an md5 sum string of this string
        public static string GetMd5Sum(string str)
        {
            // First we need to convert the string into bytes, which
            // means using a text encoder.
            Encoder enc = Encoding.Unicode.GetEncoder();

            // Create a buffer large enough to hold the string
            var unicodeText = new byte[str.Length * 2];
            enc.GetBytes(str.ToCharArray(), 0, str.Length, unicodeText, 0, true);

            // Now that we have a byte array we can ask the CSP to hash it
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] result = md5.ComputeHash(unicodeText);

            // Build the final string by converting each byte
            // into hex and appending it to a StringBuilder
            var sb = new StringBuilder();
            for (int i = 0; i < result.Length; i++)
            {
                sb.Append(result[i].ToString("X2"));
            }

            // And return it
            return sb.ToString();
        }

        public static List<string> ParseTags(string phrase)
        {
            var raw = new List<string>();
            raw.AddRange(phrase.ToLower().Split(char.Parse(" ")));

            IEnumerable<string> distinct = raw.Distinct();

            List<string> clean = CleanTags(distinct);

            return clean;
        }

        public static List<string> CleanTags(IEnumerable<string> distinct)
        {
            var ignore = new List<string> { "a", "i", "to", "in", "on", "an", "and", "the" };

            List<string> clean = (from a in distinct
                                  where !(ignore.Contains(a))
                                  select a).ToList();

            return clean;
        }
    }
}